package com.namazustudios.catfish;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.TypeLiteral;
import com.namazustudios.catfish.annotation.Entity;
import com.namazustudios.catfish.annotation.EntityKey;
import com.namazustudios.catfish.annotation.Property;
import com.namazustudios.catfish.callback.Delete;
import com.namazustudios.catfish.callback.Get;
import com.namazustudios.catfish.callback.LifecycleCallback;
import com.namazustudios.catfish.callback.Put;
import com.namazustudios.catfish.converter.Converter;
import com.namazustudios.catfish.exception.CatfishInitException;
import com.namazustudios.catfish.exception.NoSuchTypeException;
import com.namazustudios.catfish.generator.IdentityGenerator;
import com.namazustudios.catfish.provider.ValueProvider;
import com.namazustudios.catfish.validator.Validator;


final class ThreadsafeConfiguration implements Configuration {

	private static final Class<?> extractArgumentType(ParameterizedType type) {
		try {
			Type tmp = type.getActualTypeArguments()[0];

			if (tmp instanceof Class<?>) {
				return (Class<?>) tmp;
			} else if (tmp instanceof ParameterizedType) {
				return (Class<?>) ((ParameterizedType) tmp).getRawType();
			} else {
				throw new CatfishInitException("Could not extract type "
						+ "parameter from \"" + type + "\"");
			}

		} catch (ArrayIndexOutOfBoundsException ex) {
			// This shoudl probably not happen if the actual type is a
			// Get, Put, or Delete callback.
			throw new CatfishInitException("Could not extract type "
					+ "parameter from \"" + type + "\"", ex);
		}
	}

    @Inject
	private Injector injector;

    @Inject(optional = true)
    private Map<Class<?>, Put<?>> legacyPuts = Collections.emptyMap();

    @Inject(optional = true)
    private Map<Class<?>, Get<?>> legacyGets = Collections.emptyMap();

    @Inject(optional = true)
    private Map<Class<?>, Delete<?>> legacyDeletes = Collections.emptyMap();

    @Inject(optional = true)
    private Map<String, Converter<?, ?>> legacyConverters = Collections.emptyMap();

    @Inject(optional = true)
    private Map<String, Validator<?>> legacyValidators = Collections.emptyMap();

	@Inject(optional = true)
    private Map<String, ValueProvider<?>> legacyValueProviders = Collections.emptyMap();

	@Inject(optional = true)
    private Map<String, IdentityGenerator<?>> legacyIdentityGenerators = Collections.emptyMap();

	private final ConcurrentMap<String, Class<?>> kinds 
		= new ConcurrentHashMap<String, Class<?>>();

	private final ConcurrentMap<Class<?>, Key<Get<?>>> gets 
		= new ConcurrentHashMap<Class<?>, Key<Get<?>>>();

	private final ConcurrentMap<Class<?>, Key<Put<?>>> puts 
		= new ConcurrentHashMap<Class<?>, Key<Put<?>>>();

	private final ConcurrentMap<Class<?>, Key<Delete<?>>> deletes 
		= new ConcurrentHashMap<Class<?>, Key<Delete<?>>>();

	private final ConcurrentMap<String, Key<Validator<?>>> validators 
		= new ConcurrentHashMap<String, Key<Validator<?>>>();

	private final ConcurrentMap<String, Key<ValueProvider<?>>> valueProviders 
		= new ConcurrentHashMap<String, Key<ValueProvider<?>>>();

	private final ConcurrentMap<String, Key<Converter<?, ?>>> converters 
		= new ConcurrentHashMap<String, Key<Converter<?, ?>>>();

	private final ConcurrentMap<String, Key<IdentityGenerator<?>>> identityGenerators 
		= new ConcurrentHashMap<String, Key<IdentityGenerator<?>>>();

	@Override
	public void addClass(TypeLiteral<?> type) {
		Class<?> old;
		Class<?> cls = type.getRawType();
		String kind = Catfish.Utility.getKind(cls);

		if ((old = kinds.putIfAbsent(kind, cls)) != null && !old.equals(cls)) {
			throw new CatfishInitException("Kind \"" + kind + " already "
					+ "mapped to type: " + old.getName());
		}

	}

	@Override
	public void addConverter(TypeLiteral<? extends Converter<?, ?>> typeLiteral) {
		String name;
		Key<?> tmp;

		@SuppressWarnings("unchecked")
		Key<Converter<?, ?>> key = (Key<Converter<?, ?>>) Key.get(typeLiteral);

		name = Catfish.Utility.getName(typeLiteral.getRawType());

		if ((tmp = converters.putIfAbsent(name, key)) != null
				&& !key.equals(tmp)) {
			throw new CatfishInitException("Converter with name \"" + name
					+ "\" already exists with key: " + key);
		}

	}


	
	@Override
	public void addIdentityGenerator(TypeLiteral<? extends IdentityGenerator<?>> typeLiteral) {
		String name;
		Key<?> tmp;
		
		@SuppressWarnings("unchecked")
		Key<IdentityGenerator<?>> key = (Key<IdentityGenerator<?>>) Key.get(typeLiteral);

		name = Catfish.Utility.getName(typeLiteral.getRawType());

		if ((tmp = identityGenerators.putIfAbsent(name, key)) != null
				&& !key.equals(tmp)) {
			throw new CatfishInitException("Convert with name \"" + name
					+ "\" already exists with key: " + key);
		}

	}

	public void addLifecycleCallback(
			TypeLiteral<? extends LifecycleCallback> typeLiteral) {
		Class<?> callbackType;

		callbackType = typeLiteral.getRawType();

		for (Type iface : callbackType.getGenericInterfaces()) {
			ParameterizedType paramterizedType;

			if (!(iface instanceof ParameterizedType)) {
				continue;
			}

			paramterizedType = (ParameterizedType) iface;

			if (Get.class.isAssignableFrom((Class<?>) paramterizedType.getRawType())) {

				@SuppressWarnings("unchecked")
				Key<Get<?>> key = Key.get((Class<Get<?>>) callbackType);
				Class<?> argument = extractArgumentType(paramterizedType);
				addGetCallback(argument, key);

			}

			if (Put.class.isAssignableFrom((Class<?>) paramterizedType.getRawType())) {

				@SuppressWarnings("unchecked")
				Key<Put<?>> key = Key.get((Class<Put<?>>) callbackType);
				Class<?> argument = extractArgumentType(paramterizedType);
				addPutCallback(argument, key);

			}

			if (Delete.class.isAssignableFrom((Class<?>) paramterizedType.getRawType())) {

				@SuppressWarnings("unchecked")
				Key<Delete<?>> key = Key.get((Class<Delete<?>>) callbackType);
				Class<?> argument = extractArgumentType(paramterizedType);
				addDeleteCallback(argument, key);

			}

		}

	}

//	public boolean isValidatorPresent(Property property) {
//		
//	}

	@Override
	public void addValidator(TypeLiteral<? extends Validator<?>> typeLiteral) {
		String name;

		@SuppressWarnings("unchecked")
		Key<Validator<?>> tmp, key = (Key<Validator<?>>) Key.get(typeLiteral);

		name = Catfish.Utility.getName(typeLiteral.getRawType());

		if ((tmp = validators.putIfAbsent(name, key)) != null
				&& !key.equals(tmp)) {
			throw new CatfishInitException("Convert with name \"" + name
					+ "\" already exists with key: " + key);
		}

	}

	@Override
	public void addValueProvider(TypeLiteral<? extends ValueProvider<?>> typeLiteral) {
		String name;

		@SuppressWarnings("unchecked")
		Key<ValueProvider<?>> tmp, key = (Key<ValueProvider<?>>) Key.get(typeLiteral);

		name = Catfish.Utility.getName(typeLiteral.getRawType());

		if ((tmp = valueProviders.putIfAbsent(name, key)) != null
				&& !key.equals(tmp)) {
			throw new CatfishInitException("Convert with name \"" + name
					+ "\" already exists with key: " + key);
		}

	}

	@Override
	public Class<?> getClass(String kind) throws NoSuchTypeException {
		Class<?> cls = kinds.get(kind);

		if (cls == null) {
			throw new NoSuchTypeException(kind);
		}

		return cls;
	}

	@Override
	public Converter<?, ?> getConverter(Property property) {
		Key<Converter<?,?>> key;
		Converter<?, ?> converter;

		String name = Catfish.Utility.getConverterName(property);

		converter = legacyConverters.get(name);

		if (converter != null) {
			return converter;
		}

		key = converters.get(name);

		if (key == null) {
			throw new CatfishException("No converter for " +
					"property \"" + property + "\".");
		}

		return injector.getInstance(key);
	}

	@Override
	public Delete<?> getDeleteCallback(Class<?> type) {
		Key<Delete<?>> key;
		Delete<?> delete = legacyDeletes.get(type);

		if (delete != null) {
			return delete;
		}

		key = deletes.get(type);

		return key == null ? null : injector.getInstance(key);

	}

	@Override
	public Get<?> getGetCallback(Class<?> type) {
		Key<Get<?>> key;
		Get<?> get = legacyGets.get(type);

		if (get != null) {
			return get;
		}

		key = gets.get(type);

		return key == null ? null : injector.getInstance(key);

	}

	@Override
	public IdentityGenerator<?> getIdentityGenerator(EntityKey key) {
		Key<IdentityGenerator<?>> k;
		IdentityGenerator<?> identityGenerator;

		String name = key.identityGenerator();

		if (name == null || name.isEmpty()) {
			name = key.identityGeneratorType().getName();
		}

		identityGenerator = legacyIdentityGenerators.get(name);

		if (identityGenerator != null) {
			return identityGenerator;
		}

		k = identityGenerators.get(name);

		if (k == null) {
			throw new CatfishException("No converter for " +
					"entity key \"" + key + "\".");
		}

		return injector.getInstance(k); 
	}

	@Override
	public Put<?> getPutCallback(Class<?> type) {
		Key<Put<?>> key;
		Put<?> put = legacyPuts.get(type);

		if (put != null) {
			return put;
		}

		key = puts.get(type);

		return key == null ? null : injector.getInstance(key);

	}

	@Override
	public Validator<?> getValidator(Property property) {
		Key<Validator<?>> key;
		Validator<?> validator; 

		String name = Catfish.Utility.getValidatorName(property);

		validator = legacyValidators.get(name);

		if (validator != null) {
			return validator;
		}

		key = validators.get(name);

		if (key == null) {
			throw new CatfishException("No validator for " +
					"entity key \"" + key + "\".");
		}

		return injector.getInstance(key);

	}

	@Override
	public ValueProvider<?> getValueProvider(Property property) {
		Key<ValueProvider<?>> key;
		ValueProvider<?> valueProvider;

		String name = property.provider();

		if (name == null || name.isEmpty()) {
			name = property.validatorType().getName();
		}

		valueProvider = legacyValueProviders.get(name);

		if (valueProvider != null) {
			return valueProvider;
		}

		key = valueProviders.get(name);

		if (key == null) {
			throw new CatfishException("No validator for " +
					"entity key \"" + key + "\".");

		}

		return injector.getInstance(key);

	}

	private <T> void addDeleteCallback(Class<?> cls, Key<Delete<?>> key) {
		Key<Delete<?>> tmp;

		Catfish.Utility.getKind(cls);

		if ((tmp = deletes.putIfAbsent(cls, key)) != null && !tmp.equals(key)) {
			throw new CatfishInitException("Delete callback mapping already "
					+ "exists for \"" + cls + "\" using " + key);
		}

	}

	private void addGetCallback(Class<?> cls, Key<Get<?>> key) {
		Key<Get<?>> tmp;

		Catfish.Utility.getKind(cls);

		if ((tmp = gets.putIfAbsent(cls, key)) != null && !tmp.equals(key)) {
			throw new CatfishInitException("Get callback mapping already "
					+ "exists for \"" + cls + "\" using " + key);
		}

	}

	private <T> void addPutCallback(Class<?> cls, Key<Put<?>> key) {
		Key<Put<?>> tmp;

		Catfish.Utility.getKind(cls);

		if ((tmp = puts.putIfAbsent(cls, key)) != null && !tmp.equals(key)) {
			throw new CatfishInitException("Put callback mapping already "
					+ "exists for \"" + cls + "\" using " + key);
		}

	}

	@Inject
	@SuppressWarnings("unchecked")
	void setInjector(Injector injector) {
		for (Key<?> key : injector.getBindings().keySet()) {
			Class<?> raw;
			TypeLiteral<?> typeLiteral = key.getTypeLiteral();

			raw = typeLiteral.getRawType();

			if (raw.isAnnotationPresent(Entity.class)) {
				addClass(typeLiteral);
			}

			if (Converter.class.isAssignableFrom(raw)) {
				addConverter((TypeLiteral<? extends Converter<?, ?>>) typeLiteral);
			}

			if (IdentityGenerator.class.isAssignableFrom(raw)) {
				addIdentityGenerator((TypeLiteral<? extends IdentityGenerator<?>>) typeLiteral);
			}

			if (LifecycleCallback.class.isAssignableFrom(raw)) {
				addLifecycleCallback((TypeLiteral<? extends LifecycleCallback>) typeLiteral);
			}

			if (Validator.class.isAssignableFrom(raw)) {
				addLifecycleCallback((TypeLiteral<? extends LifecycleCallback>) typeLiteral);
			}

			if (ValueProvider.class.isAssignableFrom(raw)) {
				addValueProvider((TypeLiteral<? extends ValueProvider<?>>) typeLiteral);
			}

		}
	}

	@Inject(optional = true)
	void setTypes(Set<Class<?>> types) {
		for (Class<?> cls : types) {

			if (cls.equals(Void.class)) {
				continue;
			}

			String name = Catfish.Utility.getKind(cls);
			kinds.put(name, cls);

		}
	}

}
